/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ftpcombiner.filesystem;

import ftpcombiner.ApplicationController;
import ftpcombiner.gui.ApplicationFrame;
import ftpcombiner.server.ProgressBarUpdater;
import ftpcombiner.server.ServerConnection;
import ftpcombiner.server.serverconnection.File;
import ftpcombiner.server.serverconnection.Folder;
import ftpcombiner.server.serverconnection.ServerItem;
import ftpcombiner.server.virtualfiles.VirtualFile;
import ftpcombiner.server.virtualfiles.VirtualFolder;
import ftpcombiner.server.virtualfiles.VirtualItem;
import it.sauronsoftware.ftp4j.FTPAbortedException;
import it.sauronsoftware.ftp4j.FTPDataTransferException;
import it.sauronsoftware.ftp4j.FTPException;
import it.sauronsoftware.ftp4j.FTPIllegalReplyException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.swing.SwingWorker;
import javax.swing.tree.TreePath;

/**
 *
 * @author Ben
 */
public class FileSystem {
    /*
     * To change this template, choose Tools | Templates
     * and open the template in the editor.
     */

    private static FileSystem instance;
    private final Object dirLock = new Object();
//    private Traverser traverser;

    /**
     * Singleton getter method for FileSystem
     *
     * @return The singleton instance of FileSystem
     */
    public static FileSystem getInstance() {
        if (instance == null) {
            instance = new FileSystem();
        }
        return instance;
    }

//
//    public Traverser getTraverser() {
//        synchronized (dirLock) {
//            return traverser;
//        }
//    }
//
//    public class Traverser {
    private FileSystem() {
        System.out.println("Connecting to servers...");
        servers = new ServerArrayList();
//        try{
        for (int i = 0; i < ApplicationController.getInstance().getApplicationProperties().getServerProperties().size(); i++) {
            if (!ApplicationController.getInstance().getApplicationProperties().getServerProperties().get(i).getUrl().equals("")) {
                servers.add(new ServerConnection(ApplicationController.getInstance().getApplicationProperties().getServerProperties().get(i).getUrl(), ApplicationController.getInstance().getApplicationProperties().getServerProperties().get(i).getUsername(), ApplicationController.getInstance().getApplicationProperties().getServerProperties().get(i).getPassword()));
                servers.get(i).connectAndLogin();
            }
        }
//        }catch(SocketTimeoutException ex){
//            System.err.println("Could not connect to servers, exiting...");
//        }
        ApplicationController.getSplashScreen().incrementProgress();
        System.out.println("Retrieving files...");
        curDir = new VirtualFolder(null, null);
        curDir.setChildren(convert(getCurrentServerFiles(), curDir));
        ApplicationController.getSplashScreen().incrementProgress();
        System.out.println("Servers ready.");
    }
    VirtualFolder curDir;

    public void changeServerDirectory(final String dir) {
        //ApplicationController.getInstance().getApplicationFrame().getStatusBar().paintImmediately(0, 0, ApplicationController.getInstance().getApplicationFrame().getStatusBar().getWidth(), ApplicationController.getInstance().getApplicationFrame().getStatusBar().getHeight());
        for (ServerConnection server : servers) {
            try {
                server.changeDirectory(dir);
                server.updateCurrentFiles();
            } catch (IllegalStateException | IOException | FTPIllegalReplyException | FTPException ex) {
                System.err.println(ex.getMessage());
            }

        }
    }

    public synchronized void changeDirectory(final String path) {
        changeDirectory(path, true, null);
    }

    /**
     * Changes the directory of the file system to the specified directory or
     * path.
     *
     * Note: This method will sometimes take a long time to change to absolute
     * directories if they have not been loaded into memory yet.
     *
     * @param path
     * @throws IllegalArgumentException If the directory specified does not
     * exist.
     */
    public synchronized void changeDirectory(final String path, final boolean updateFileTable, final TreePath treePath) {
        //System.out.println("change");
        final int increment = 100 / (path.contains("/") ? (path.split("/").length == 0 ? 1 : path.split("/").length) * 2 * servers.size() : 2 * servers.size());
        final ProgressBarUpdater prog = ProgressBarUpdater.getInstance(100);
        prog.started();
        (new SwingWorker() {
            @Override
            protected Object doInBackground() throws Exception {
                addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        if ("progress".equals(evt.getPropertyName())) {
                            prog.transferred((int) evt.getNewValue() - (int) evt.getOldValue());
                        }
                    }
                });
                if (path.contains("/")) {
                    curDir = getRootDir();
                    for (String p : path.split("/")) {
                        if (!p.equals("")) {
                            Object ob = ((VirtualFolder) curDir.getChild(p)).getChildren();
                            if (((VirtualFolder) curDir.getChild(p)).getChildren() == null) {
                                try {
                                    for (ServerConnection server : servers) {
                                        try {
                                            server.changeDirectory(curDir.getChild(p).getPath());
                                            setProgress(getProgress() + increment);
                                            server.updateCurrentFiles();
                                            setProgress(getProgress() + increment);
                                        } catch (IllegalStateException | IOException | FTPIllegalReplyException | FTPException ex) {
                                            prog.failed();
                                            //System.err.println("asdfasdfilr");
                                        }

                                    }
                                } catch (ClassCastException ex) {
                                    prog.failed();
                                    throw new IllegalArgumentException(ex);
                                }
                                ((VirtualFolder) curDir.getChild(p)).setChildren(convert(getCurrentServerFiles(), (VirtualFolder) curDir.getChild(p)));
                            }
                            try {
                                curDir = (VirtualFolder) curDir.getChild(p);
                            } catch (ClassCastException ex) {
                                prog.failed();
                                throw new IllegalArgumentException(ex);
                            }
                        }
                    }
                    //throw new UnsupportedOperationException("VirtualFileSystem.changeDirectory() does not yet support absolute paths.");
                } else {
                    System.out.println("curDir two");
                    if (((VirtualFolder) curDir.getChild(path)).getChildren() == null) {
                        try {
                            for (ServerConnection server : servers) {
                                try {
                                    server.changeDirectory(curDir.getChild(path).getPath());
                                    setProgress(getProgress() + increment);
                                    server.updateCurrentFiles();
                                    setProgress(getProgress() + increment);
                                } catch (IllegalStateException | IOException | FTPIllegalReplyException | FTPException ex) {
                                    ApplicationController.getInstance().setAppStatus("Change directory failed");
                                    prog.failed();

                                }

                            }
                        } catch (ClassCastException ex) {
                            prog.failed();
                            throw new IllegalArgumentException(ex);
                        }
                        ((VirtualFolder) curDir.getChild(path)).setChildren(convert(getCurrentServerFiles(), (VirtualFolder) curDir.getChild(path)));
                    }
                    try {
                        curDir = (VirtualFolder) curDir.getChild(path);
                    } catch (ClassCastException ex) {
                        prog.failed();
                        throw new IllegalArgumentException(ex);
                    }
                }
                return null;
            }

            @Override
            protected void done() {
                if (updateFileTable) {
                    System.out.println("update");
                    ApplicationController.getInstance().getApplicationFrame().updateFileList();
                }
                if (treePath != null) {
                    ApplicationController.getInstance().getApplicationFrame().getFrameSplitter().getFileTree().update(treePath);
                }
                prog.completed();
            }
        }).execute();
    }

    public VirtualFolder getRootDir() {
        VirtualFolder folder = curDir;
        while (!folder.isRoot()) {
            folder = folder.getParent();
        }
        return folder;
    }

    public VirtualItem[] getCurrentFiles() {
        //System.out.println("CurDirchildren: "+curDir.getChildren());
        return curDir.getChildren();
    }

    /**
     * Renames a file or folder. This method also handles all associated UI
     * changes (ie. progress bar, status, file list updating).
     *
     * @param item The VirtualItem to rename
     * @param newName The new name for the file or folder.
     */
    public void rename(final VirtualItem file, final String newName) {
        final int increment = 100 / (file instanceof VirtualFile ? 3 : (servers.size() * 2) + 1);
        final ProgressBarUpdater p = ProgressBarUpdater.getInstance(100);
        ApplicationController.getInstance().setAppStatus("Renaming...", true);
        p.started();
        (new SwingWorker() {
            @Override
            protected Object doInBackground() throws Exception {
                addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        if ("progress".equals(evt.getPropertyName())) {
                            p.transferred((int) evt.getNewValue() - (int) evt.getOldValue());
                        }
                    }
                });
                try {
                    changeServerDirectory(file.getParent().getPath());
                    setProgress(increment);
                    if (file instanceof VirtualFile) {
                        file.getAlias().getServer().rename(file.getName(), newName);
                        setProgress(getProgress() + increment);
                        file.getAlias().getServer().updateCurrentFiles();
                        setProgress(getProgress() + increment);
                    } else {
                        for (ServerConnection s : servers) {
                            s.rename(file.getName(), newName);
                            setProgress(getProgress() + increment);
                            s.updateCurrentFiles();
                            setProgress(getProgress() + increment);
                        }
                    }
                    file.getParent().setChildren(convert(getCurrentServerFiles(), file.getParent()));
                } catch (IllegalStateException | IOException | FTPIllegalReplyException | FTPException ex) {
                    ApplicationController.getInstance().setAppStatus("Rename Failed");
                }
                return null;
            }

            @Override
            protected void done() {
                p.completed();
                if (file.getParent().getPath().equals(curDir.getPath())) {
                    ApplicationController.getInstance().getApplicationFrame().updateFileList();
                }
                ApplicationController.getInstance().setAppStatus("Ready");
            }
        }).execute();


    }

    public void changeDirectoryUp() {
        if (!curDir.isRoot()) {
            curDir = curDir.getParent();
        }
        ApplicationController.getInstance().getApplicationFrame().updateFileList();
    }

    /**
     * Converts an array of ServerItems to VirtualItems with a parent specified
     * by the<code> parent </code>parameter.
     *
     * @param items The ServerItems to convert
     * @param parent The parent for the VirtualItems
     * @return The converted ServerItems as VirtualItems
     */
    public static VirtualItem[] convert(ServerItem[] items, VirtualFolder parent) {
        VirtualItem[] virtualItems = new VirtualItem[items.length];
        for (int i = 0; i < items.length; i++) {
            virtualItems[i] = items[i] instanceof Folder ? new VirtualFolder((Folder) items[i], parent) : new VirtualFile((File) items[i], parent);
        }
        return virtualItems;
    }

    /*-************************************************************************************************************************
     ********************* END OF OLD VirtualFileSystem CLASS; START OF OLD TransactionManager CLASS!! ************************
     **************************************************************************************************************************/
    private ServerArrayList servers;

    public ServerConnection getServer(int index) {
        return servers.get(index);
    }

    private String getCurrentServerDir() {
        try {
            return servers.get(0).currentDirectory();
        } catch (IllegalStateException | IOException | FTPIllegalReplyException | FTPException ex) {
        }
        return "";
    }

    public VirtualFolder getCurrentDir() {
        return curDir;
    }

    public int getServerCount() {
        return servers.size();
    }

    public ServerArrayList getServers() {
        return servers;
    }

    public void addOrSetServer(int index, ServerConnection server) {
        servers.addOrSet(index, server);
    }

    public static class ServerArrayList extends ArrayList<ServerConnection> {

        public ServerArrayList(int initialCapacity) {
            super(initialCapacity);
        }

        public ServerArrayList() {
            super();
        }

        private void addOrSet(int index, ServerConnection element) {
            try {
                set(index, element);
            } catch (IndexOutOfBoundsException e) {
                add(index, element);
            }
        }
    }

    public void upload(final java.io.File file) {
        ApplicationController.getInstance().setAppStatus("Uploading " + file.getName(), true);
        (new SwingWorker() {
            @Override
            protected Object doInBackground() throws Exception {
                for (ServerConnection connection : servers) {
                    try {
                        if (ApplicationController.getInstance().isBackupMode()) {
                            connection.upload(file);
                        } else {
                            connection.upload(file, ProgressBarUpdater.getInstance(file));
                        }
                        connection.updateCurrentFiles();
                        break;
                    } catch (IllegalStateException | FTPIllegalReplyException | FTPDataTransferException | FTPAbortedException | IOException ex) {
                        ApplicationController.getInstance().setAppStatus("Upload Failed");
                    } catch (FTPException ex) {
                        if (ex.getCode() == 452 || ex.getCode() == 552) {
                        } else {
                            ApplicationController.getInstance().setAppStatus("Upload Failed: Out of space");
                            break;
                        }
                    }
                }
                curDir.setChildren(convert(getCurrentServerFiles(), curDir));
                return null;
            }

            @Override
            protected void done() {
                ApplicationController.getInstance().getApplicationFrame().updateFileList();
                ApplicationController.getInstance().setAppStatus("Finished uploading " + file.getName());
            }
        }).execute();
    }

    /**
     * Downloads a File from the server onto the local computer. This method
     * takes a {@link VirtualFile} for a parameter instead of a String as in
     * {@link ServerConnection#download}. Also this method automatically handles
     * the {@link ApplicationFrame}'s progress bar and catches exceptions that
     * are thrown by {@linkplain ServerConnection#download}.
     *
     * @param file The {@link File} on the server to download
     * @param localFile The directory on the local computer into which the file
     * should be placed
     *
     * @see ServerConnection#download(java.lang.String, java.io.File,
     * it.sauronsoftware.ftp4j.FTPDataTransferListener)
     */
    public void download(final VirtualFile file, final java.io.File localFile) {
        ApplicationController.getInstance().setAppStatus("Downloading " + file.getName(), true);
        (new SwingWorker() {
            @Override
            protected Object doInBackground() throws Exception {
                try {
                    file.getAlias().getServer().download(file.getName(), localFile, ProgressBarUpdater.getInstance(file.getAlias()));
                } catch (IllegalStateException | FTPIllegalReplyException | FTPException | FTPDataTransferException | FTPAbortedException | IOException ex) {
                    ApplicationController.getInstance().setAppStatus("Download Failed: " + ex.getMessage());
                }
                return null;
            }

            @Override
            protected void done() {
                ApplicationController.getInstance().setAppStatus("Finished downloading " + localFile.getName());
            }
        }).execute();
    }

    public void disconnect() {
        for (ServerConnection server : servers) {
            try {
                server.disconnect(true);
            } catch (IllegalStateException | IOException | FTPIllegalReplyException | FTPException ex) {
            }
        }
    }

    public void updateCurrentFiles() {
        curDir.setChildren(convert(getCurrentServerFiles(), curDir));
    }

    private ServerItem[] getCurrentServerFiles() {
        ServerItem[] arr = {};
        for (ServerConnection server : servers) {
            arr = concat(arr, server.getFiles());
        }
        Set<ServerItem> arrSet = new LinkedHashSet<>(Arrays.asList(arr));
        return arrSet.toArray(new ServerItem[0]);
    }

    private static <T> T[] concat(T[] first, T[] second) {
        T[] result = Arrays.copyOf(first, first.length + second.length);
        System.arraycopy(second, 0, result, first.length, second.length);
        return result;
    }

    private void changeServerDirectoryUp() {
        for (ServerConnection server : servers) {
            try {
                server.changeDirectoryUp();
                server.updateCurrentFiles();
            } catch (IllegalStateException | IOException | FTPIllegalReplyException | FTPException ex) {
                System.err.println(ex.getMessage());
            }

        }
    }

    /**
     * Creates a new directory and handles all UI changes including the naming
     * of the new folder.
     */
    public void createDirectory() {
        final int increment = 100 / ((servers.size() * 2) + 1);
        final ProgressBarUpdater p = ProgressBarUpdater.getInstance(100);
        int i = 2;
        String tempName = "New Folder";
        for (VirtualItem item : FileSystem.getInstance().getCurrentFiles()) {
            if (item instanceof VirtualFolder) {
                if (item.getName().equals(tempName)) {
                    tempName = "New Folder (" + i + ")";
                    i++;
                }
            }
        }
        final String name = tempName;
        p.started();
        ApplicationController.getInstance().setAppStatus("Creating directory...", true);
        (new SwingWorker() {
            @Override
            protected Object doInBackground() throws Exception {
                addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        if ("progress".equals(evt.getPropertyName())) {
                            p.transferred((int) evt.getNewValue() - (int) evt.getOldValue());
                        }
                    }
                });

                changeServerDirectory(curDir.getPath());
                setProgress(increment);
                for (ServerConnection server : servers) {
                    try {
                        server.createDirectory(name);
                        setProgress(getProgress() + increment);
                        server.updateCurrentFiles();
                        setProgress(getProgress() + increment);
                    } catch (IllegalStateException | IOException | FTPIllegalReplyException | FTPException ex) {
                        ApplicationController.getInstance().setAppStatus("Directory creation failed");
                    }
                }
                updateCurrentFiles();
                return null;
            }

            @Override
            protected void done() {
                ApplicationController.getInstance().getApplicationFrame().updateFileList();
                p.completed();
                ApplicationController.getInstance().setAppStatus("Directory created");
                ApplicationController.getInstance().getApplicationFrame().getFrameSplitter().getFileTable().editCellAt(curDir.getChild(name).getIndex() + 1, 0);
            }
        }).execute();

    }

    public void delete(final VirtualFolder folder) {
        final int increment = 100 / ((2 * servers.size()) + 2);
        final ProgressBarUpdater p = ProgressBarUpdater.getInstance(100);
        ApplicationController.getInstance().setAppStatus("Deleting " + folder.getName(), true);
        p.started();
        (new SwingWorker() {
            @Override
            protected Object doInBackground() throws Exception {
                addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        if ("progress".equals(evt.getPropertyName())) {
                            p.transferred((int) evt.getNewValue() - (int) evt.getOldValue());
                        }
                    }
                });
                changeServerDirectory(folder.getPath());
                setProgress(increment);
                //if (getCurrentServerFiles().length != 0) {
                //ApplicationController.getInstance().setAppStatus("Delete Failed: Directory is not empty");
                //} else {
                try {
                    changeServerDirectoryUp();
                    setProgress(getProgress() + increment);
                    for (ServerConnection server : servers) {
                        server.deleteDirectory(folder.getName());
                        setProgress(getProgress() + increment);
                        server.updateCurrentFiles();
                        setProgress(getProgress() + increment);
                    }
                    //folder.getAlias().deleteDirectory(folder.getName());

                    curDir.setChildren(convert(getCurrentServerFiles(), curDir));
                    // }
                    //System.out.println("delte finshed");
                    ApplicationController.getInstance().setAppStatus("Finished deleting " + folder.getName());
                } catch (FTPException e) {
                    ApplicationController.getInstance().setAppStatus("Delete Failed: " + e.getMessage());
                    p.failed();
                } catch (IllegalStateException | IOException | FTPIllegalReplyException ex) {
                    //System.out.println("delete failed");
                    ApplicationController.getInstance().setAppStatus("Delete Failed");
                    p.failed();
                }
                return null;
            }

            @Override
            protected void done() {
                ApplicationController.getInstance().getApplicationFrame().updateFileList();
                p.completed();
            }
        }).execute();
    }

    public void delete(final VirtualFile file) {
        //System.out.println("delete");
        final int increment = 100 / 3;
        final ProgressBarUpdater p = ProgressBarUpdater.getInstance(100);
        ApplicationController.getInstance().setAppStatus("Deleting " + file.getName(), true);
        p.started();
        (new SwingWorker() {
            @Override
            protected Object doInBackground() throws Exception {
                addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        if ("progress".equals(evt.getPropertyName())) {
                            p.transferred((int) evt.getNewValue() - (int) evt.getOldValue());
                        }
                    }
                });
                try {
                    changeServerDirectory(file.getParent().getPath());
                    setProgress(increment);
                    file.getAlias().getServer().deleteFile(file.getName());
                    setProgress(getProgress() + increment);
                    file.getAlias().getServer().updateCurrentFiles();
                    setProgress(getProgress() + increment);
                    curDir.setChildren(convert(getCurrentServerFiles(), curDir));
                    ApplicationController.getInstance().setAppStatus("Finished deleting " + file.getName());
                } catch (IllegalStateException | IOException | FTPIllegalReplyException | FTPException ex) {
                    ApplicationController.getInstance().setAppStatus("Delete Failed");
                    p.failed();
                }
                return null;
            }

            @Override
            protected void done() {
                ApplicationController.getInstance().getApplicationFrame().updateFileList();

                p.completed();
            }
        }).execute();

    }
}
